package guowei.guliorder.order.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import guowei.common.to.mq.OrderVo;
import guowei.common.utils.R;
import guowei.common.vo.MemberVo;
import guowei.guliorder.order.Constant.OrderConstant;

import guowei.guliorder.order.entity.OrderItemEntity;
import guowei.guliorder.order.entity.PaymentInfoEntity;
import guowei.guliorder.order.exception.WareNotEnoughException;
import guowei.guliorder.order.feign.CartFeign;
import guowei.guliorder.order.feign.ProductFeign;
import guowei.guliorder.order.feign.StorageFeign;
import guowei.guliorder.order.feign.UserFeign;
import guowei.guliorder.order.intercaptor.OrderInterceptor;
import guowei.guliorder.order.service.OrderItemService;
import guowei.guliorder.order.service.PaymentInfoService;
import guowei.guliorder.order.vo.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import guowei.common.utils.PageUtils;
import guowei.common.utils.Query;

import guowei.guliorder.order.dao.OrderDao;
import guowei.guliorder.order.entity.OrderEntity;
import guowei.guliorder.order.service.OrderService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;


@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {

    public static final ThreadLocal<OrderSubmitVo> threadLocal1 = new ThreadLocal<>();

    @Autowired
    CartFeign cartFeign;

    @Autowired
    ProductFeign productFeign;

    @Autowired
    UserFeign userFeign;

    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    StorageFeign storageFeign;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    private OrderItemService orderItemService;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    PaymentInfoService paymentInfoService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );
        return new PageUtils(page);
    }

    /**
     * 得到页面所需所有信息
     *  异步处理
     *  解决feign远程调用丢失请求头
     *  解决异步feign丢失请求头
     * @return
     */
    @Override
    public OrderConfirmVo getAllOrderInfo() throws ExecutionException, InterruptedException {
        MemberVo memberVo = OrderInterceptor.threadLocal.get();

        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();

//  测试一个非异步方式调用得到老请求数据  成功
//        List<OrderItemVo> orderList1 = cartFeign.getOrderListNoKey();
//        System.out.println(orderList1);

//  先利用RequestContextHolder拿到刚进来的请求数据。包括请求头信息。
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

//  异步编排实现远程调用1
        CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
            //  1.查询所有购物车数据
            System.out.println("CartFeign的线程id为:"+Thread.currentThread().getId());
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> orderList = cartFeign.getOrderListNoKey();
            List<OrderItemVo> orderItemVos = orderList.stream().map(orderItemVo -> {
//  3.根据指定skuId查询实时价格。
                System.out.println("ProductFeign的线程id为:"+Thread.currentThread().getId());
                BigDecimal price = productFeign.getNewPriceBySkuId(orderItemVo.getSkuId());
                orderItemVo.setPrice(price);
                return orderItemVo;
            }).collect(Collectors.toList());
            orderConfirmVo.setItems(orderItemVos);
        }, executor).thenRunAsync(()->{
            List<OrderItemVo> items = orderConfirmVo.getItems();
            List<Long> ids = items.stream().map(item -> {
                Long skuId = item.getSkuId();
                return skuId;
            }).collect(Collectors.toList());
            R stock = storageFeign.getInfoBySkuIds(ids);
            List<HasStockVo> vos = (List<HasStockVo>) stock.getData("vos", new TypeReference<List<HasStockVo>>() {});
            Map<Long, Boolean> map = vos.stream().collect(Collectors.toMap(HasStockVo::getSkuId, HasStockVo::getHasStock));
            orderConfirmVo.setBoolStocks(map);
        });


        CompletableFuture<Void> runAsync1 = CompletableFuture.runAsync(() -> {
            //  2.查询所有地址信息
            System.out.println("userFeign的线程id为:"+Thread.currentThread().getId());
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberAddressVo> memberAddressVos = userFeign.getAddressBuMemberId(memberVo.getId());
            orderConfirmVo.setAddress(memberAddressVos);
        }, executor);

//  阻塞，直到所有异步任务完成。
        CompletableFuture.allOf(runAsync1,runAsync).get();

//  3. 设置优惠积分信息
        orderConfirmVo.setIntegration(memberVo.getIntegration());
//  4.设置订单总金额
//  5.设置应付金额
//  6.设置防重令牌     redis中保存token   浏览器端得到token       key为 token:用户id
        String token = UUID.randomUUID().toString().replace("-", "");
        redisTemplate.opsForValue().set(OrderConstant.ORDER_TOKEN_REDIS_PREFIX+memberVo.getId(),token,30, TimeUnit.MINUTES);
        orderConfirmVo.setOrderToken(token);
        return orderConfirmVo;
    }

    /**
     *  利用lua脚本验证token的正确与否。 并且保证token的获取，比较，删除是原子性的
     *   异步方式编排
     * @param vo
     * @return
     */
//    @GlobalTransactional  //使用seata来控制分布式事务
    @Override
    @Transactional      //事务方法，保证锁定库存出错的话，订单以及订单项数据回回滚
    public SubmitOrderRespnseVo submitRespons(OrderSubmitVo vo) {
        SubmitOrderRespnseVo submitOrderRespnseVo = new SubmitOrderRespnseVo();
        submitOrderRespnseVo.setCode(0);
        threadLocal1.set(vo);

        MemberVo memberVo = OrderInterceptor.threadLocal.get();

    //下单:去创建订单，验令牌，验价格，锁库存....
        String orderToken = vo.getOrderToken();
    // 1、验证令牌【令牌的对比和删除必须保证原子性】
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(redisScript, Arrays.asList(OrderConstant.ORDER_TOKEN_REDIS_PREFIX + memberVo.getId()), orderToken);
     // o令牌失败
        if (result == 0L){
            submitOrderRespnseVo.setCode(1);
        }else {
    //令牌验证成功
//  创建出OrderVo
            submitOrderRespnseVo.setCode(0);
            OrderCreateVo createVo = new OrderCreateVo();
//  1.设置订单信息 OrderEntity
            String timeId = IdWorker.getTimeId();
            OrderEntity orderEntity = getOrderEntity(timeId);
//  2.构建订单项信息
            List<OrderItemEntity> listItems = getAllItems(timeId);

//  3.计算价格，积分相关信息
            computeTotalPrice(orderEntity, listItems);
            createVo.setOrder(orderEntity);
            createVo.setOrderItems(listItems);
//  4.验价 与前端提交来的验证      若相差不查过0.01 就认为是一致的
            BigDecimal payPrice1 = vo.getPayPrice();
            BigDecimal payPrice2 = createVo.getOrder().getPayAmount();
            if (Math.abs(payPrice1.subtract(payPrice2).doubleValue()) < 0.01){
                //一致的情况
//  5.保存订单信息，各个订单项信息
                OrderEntity order = createVo.getOrder();
                List<OrderItemEntity> orderItems = createVo.getOrderItems();
                this.save(order);
                orderItemService.saveBatch(orderItems);
//  6.锁定库存     设置好要锁定的库存项
                OrderStorageLockVo orderStorageLockVo = new OrderStorageLockVo();
                List<OrderItemEntity> lockList = orderItems.stream().map(item -> {
                    OrderItemEntity lock = new OrderItemEntity();
                    Long skuId = item.getSkuId();
                    String skuName = item.getSkuName();
                    Integer skuQuantity = item.getSkuQuantity();
                    lock.setSkuId(skuId);
                    lock.setSkuQuantity(skuQuantity);
                    lock.setSkuName(skuName);
                    return item;
                }).collect(Collectors.toList());
                orderStorageLockVo.setLocks(lockList);
                orderStorageLockVo.setOrderSn(order.getOrderSn());
    //调用存储远程服务锁库存   若r的状态码为0则表示锁库存成功，否则表示锁库存失败
                R r = storageFeign.lockItemStorage(orderStorageLockVo);
                if (r.getCode() == 0){
                    submitOrderRespnseVo.setOrder(createVo.getOrder());

//                  todo 模拟远程扣减积分出异常
//                    int i = 10 /0;  //订单回滚，库存不回滚
//      此时是创建订单成功   发消息订单创建成功
                    rabbitTemplate.convertAndSend("order-event-exchange","order.create.order",createVo.getOrder());

                    return submitOrderRespnseVo;
                }else {
    //锁定库存失败
                    submitOrderRespnseVo.setCode(3);
                    throw new WareNotEnoughException();
                }
                //  验证令牌失败
            }else {
            //验价失败
                submitOrderRespnseVo.setCode(2);
            }
        }
        return submitOrderRespnseVo;
    }

    @Override
    public OrderEntity getByOrderSn(String orderSn) {
        OrderEntity one = this.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
        return one;
    }


    private void computeTotalPrice(OrderEntity orderEntity, List<OrderItemEntity> listItems) {
//          优惠信息，打折信息，分解金额信息，积分，成长值信息
        BigDecimal promotionAmountAll = new BigDecimal("0.0");
        BigDecimal couponAmountAll = new BigDecimal("0.0");
        BigDecimal integrationAmountAll = new BigDecimal("0.0");
        BigDecimal realAmountAll = new BigDecimal("0.0");
        Integer integraionGift = new Integer(0);
        Integer growthGift = new Integer(0);
        for (OrderItemEntity order:listItems) {
            promotionAmountAll = promotionAmountAll.add(order.getPromotionAmount());
            couponAmountAll = couponAmountAll.add(order.getCouponAmount());
            integrationAmountAll = integrationAmountAll.add(order.getIntegrationAmount());
            realAmountAll = realAmountAll.add(order.getRealAmount());
            integraionGift = integraionGift + order.getGiftIntegration();
            growthGift = growthGift + order.getGiftGrowth();
        }
        orderEntity.setPromotionAmount(promotionAmountAll);
        orderEntity.setCouponAmount(couponAmountAll);
        orderEntity.setIntegrationAmount(integrationAmountAll);
        orderEntity.setGrowth(growthGift);
        orderEntity.setIntegration(integraionGift);
        orderEntity.setTotalAmount(realAmountAll);
        orderEntity.setPayAmount(realAmountAll.add(orderEntity.getFreightAmount()));
        orderEntity.setDeleteStatus(0); //未删除状态
        orderEntity.setStatus(0);  //未付款状态
    }

    /**
     * 得到当前订单包含的所有订单
     * @return
     */
    private List<OrderItemEntity> getAllItems(String timeId) {
//  调用购物车服务得到所有订单商品信息
        List<OrderItemVo> orderListNoKey = cartFeign.getOrderListNoKey();
        List<OrderItemEntity> itemEntities = orderListNoKey.stream().map(item -> {

//  为每一个 item设置所需的属性
            OrderItemEntity orderItemEntity = setEveryItemInfo(item);
            orderItemEntity.setOrderSn(timeId);

            return orderItemEntity;
        }).collect(Collectors.toList());
        return itemEntities;
    }

    private OrderItemEntity setEveryItemInfo(OrderItemVo item) {
        OrderItemEntity orderItemEntity = new OrderItemEntity();

//  设置商品订单号
//  设置商品的spu信息
        R r = productFeign.getSpuInfoBySkuId(item.getSkuId());
        SpuInfoVo spuInfo = (SpuInfoVo) r.getData("spuInfo", new TypeReference<SpuInfoVo>() {
        });
        orderItemEntity.setSpuId(spuInfo.getId());
        orderItemEntity.setSpuName(spuInfo.getSpuName());
//        orderItemEntity.setSpuPic(spuInfo.get);   //商品图片
        orderItemEntity.setCategoryId(spuInfo.getCatalogId());

        R info = productFeign.brandInfo(spuInfo.getBrandId());
        BrandVo brand = (BrandVo) info.getData("brand", new TypeReference<BrandVo>() {
        });
        orderItemEntity.setSpuBrand(brand.getName());

//  设置积分信息
        orderItemEntity.setGiftGrowth(item.getPrice().multiply(new BigDecimal(item.getCount().toString())).intValue());
        orderItemEntity.setGiftIntegration(item.getPrice().multiply(new BigDecimal(item.getCount().toString())).intValue());
//  设置sku信息
        orderItemEntity.setSkuId(item.getSkuId());
        orderItemEntity.setSkuName(item.getTitle());
        orderItemEntity.setSkuPic(item.getImage());
        orderItemEntity.setSkuPrice(item.getPrice());
        orderItemEntity.setSkuQuantity(item.getCount());
        String s = StringUtils.collectionToDelimitedString(item.getSkuAttr(), ";");
        orderItemEntity.setSkuAttrsVals(s);
    //价格信息
        orderItemEntity.setPromotionAmount(new BigDecimal("0.0"));
        orderItemEntity.setCouponAmount(new BigDecimal("0.0"));
        orderItemEntity.setIntegrationAmount(new BigDecimal("0.0"));
        BigDecimal totalPrice = orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString()));
        BigDecimal payPrice = totalPrice.subtract(orderItemEntity.getPromotionAmount())
                .subtract(orderItemEntity.getCouponAmount())
                .subtract(orderItemEntity.getIntegrationAmount());
        orderItemEntity.setRealAmount(payPrice);
        return orderItemEntity;
//  设置优惠信息
    }


    /**
     * 此方法用于设置orderEntity信息     需要得到addrId
     * @param timeId
     */
    private OrderEntity getOrderEntity(String timeId) {
        OrderSubmitVo orderSubmitVo = threadLocal1.get();
        Long addrId = orderSubmitVo.getAddrId();
        MemberVo memberVo = OrderInterceptor.threadLocal.get();

//得到了addrId 就可以查地址了
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(timeId);
        orderEntity.setMemberId(memberVo.getId());
        R r = storageFeign.getAddrAndFareByAddrId(addrId);
        AddrAndFareVo address = (AddrAndFareVo) r.getData("data", new TypeReference<AddrAndFareVo>() {
        });
        orderEntity.setReceiverName(address.getAddress().getName());
        orderEntity.setReceiverPhone(address.getAddress().getPhone());
        orderEntity.setReceiverName(address.getAddress().getName());
        orderEntity.setReceiverProvince(address.getAddress().getProvince());
        orderEntity.setReceiverCity(address.getAddress().getCity());
        orderEntity.setReceiverRegion(address.getAddress().getRegion());
        orderEntity.setReceiverDetailAddress(address.getAddress().getDetailAddress());
        orderEntity.setFreightAmount(address.getFare());
        orderEntity.setModifyTime(new Date());

        return orderEntity;
    }

    /**
     * 修改订单状态，由待付款变为已取消。  只要订单解锁成功就给库存队列发送消息
     * @param orderEntity
     */
    @Override
    public void rollbackMq(OrderEntity orderEntity) {
//当订单状态为未支付 就修改订单状态 并通知库存修改信息

//  得到最新的订单数据
        Long orderId = orderEntity.getId();
        OrderEntity order1 = this.getById(orderId);

        if (order1.getStatus() == 0){
            OrderEntity order = new OrderEntity();
            BeanUtils.copyProperties(order1,order);
            order.setStatus(4);
            this.updateById(order);

            System.out.println("需要修改订单状态为已取消状态，并发送消息回滚库存");

            OrderVo orderVo = new OrderVo();
            BeanUtils.copyProperties(order,orderVo);
//主消息，订单服务只要一创建完成就给库存服务发送消息，让其验证是否需要回滚
// 库存服务里面自己发的消息是辅消息，主辅并存
            //这个是只要订单解锁成功就给库存队列发送消息
            rabbitTemplate.convertAndSend("stock-event-exchange","order.stock.unlock",orderVo);
        }else {
            //否则订单就已经支付， 订单状态为1  此时不需要解锁库存
            System.out.println("订单已被支付，无需修改订单状态和库存信息");
        }

    }


    //根据订单号得到 订单名称 付款金额 商品描述
    @Override
    public PayVo getPayVoByOrderSn(String orderSn) {

        PayVo payVo = new PayVo();
        OrderEntity order = this.getByOrderSn(orderSn);
        payVo.setOut_trade_no(orderSn);
        payVo.setSubject("支付ing");
        BigDecimal bigDecimal = order.getPayAmount().setScale(2, BigDecimal.ROUND_UP);//两位小数，若有以后的小数则
        payVo.setTotal_amount(bigDecimal.toString());
        payVo.setBody("");
        return payVo;
    }

    //  此方法返回分页数据 包括当前用户所用订单，以及包含的订单的商品列表
    @Override
    public PageUtils getOrdersByMemberId(Map<String, Object> params) {
        MemberVo memberVo = OrderInterceptor.threadLocal.get();

        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>().eq("member_id",memberVo.getId()).orderByDesc("id")
        );
        List<OrderEntity> order_sn = page.getRecords().stream().map(order -> {
            List<OrderItemEntity> list = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", order.getOrderSn()));
            order.setItemList(list);
            return order;
        }).collect(Collectors.toList());
        page.setRecords(order_sn);

        return new PageUtils(page);
    }

    /**
     * 处理支付宝支付结果，正确支付就返回success
     * @param payAsyncVo
     * @return
     */
    @Override
    public String dealWithAliPay(PayAsyncVo payAsyncVo) {

//  保存交易流水信息
        PaymentInfoEntity info = new PaymentInfoEntity();
        info.setOrderSn(payAsyncVo.getOut_trade_no());
        info.setAlipayTradeNo(payAsyncVo.getTrade_no());
        info.setTotalAmount(new BigDecimal(payAsyncVo.getTotal_amount()));
        info.setPaymentStatus(payAsyncVo.getTrade_status());
        info.setCallbackTime(payAsyncVo.getNotify_time());

        paymentInfoService.save(info);

//  这两种状态时支付成功
        if (payAsyncVo.getTrade_status().equals("TRADE_SUCCESS") || payAsyncVo.getTrade_status().equals("TRADE_FINISHED")){
            this.baseMapper.updateOrderSnStatus(payAsyncVo.getOut_trade_no(),1);
        }
        return "success";
    }


}